using System;
using System.ComponentModel;

namespace Microsoft.Xna.Framework
{
	[Serializable]
	public class Curve
	{
		#region Private Fields

		private CurveKeyCollection keys;
		private CurveLoopType postLoop;
		private CurveLoopType preLoop;

		#endregion Private Fields


		#region Public Properties
		public void ComputeTangent(int keyIndex, CurveTangent tangentInType, CurveTangent tangentOutType)
		{
			throw new NotImplementedException();
		}

		public void ComputeTangent(int keyIndex, CurveTangent tangentType)
		{
			this.ComputeTangent(keyIndex, tangentType, tangentType);
		}

		public void ComputeTangents(CurveTangent tangentInType, CurveTangent tangentOutType)
		{
			throw new NotImplementedException();
		}

		public void ComputeTangents(CurveTangent tangentType)
		{
			this.ComputeTangents(tangentType, tangentType);
		} 
 
		public bool IsConstant
		{
			get { return this.keys.Count <= 1; }
		}

		public CurveKeyCollection Keys
		{
			get { return this.keys; }
		}

		public CurveLoopType PostLoop
		{
			get { return this.postLoop; }
			set { this.postLoop = value; }
		}

		public CurveLoopType PreLoop
		{
			get { return this.preLoop; }
			set { this.preLoop = value; }
		}

		#endregion Public Properties


		#region Public Constructors

		public Curve()
		{
			this.keys = new CurveKeyCollection();
		}

		#endregion Public Constructors


		#region Public Methods

		public Curve Clone()
		{
			Curve curve = new Curve();

			curve.keys = this.keys.Clone();
			curve.preLoop = this.preLoop;
			curve.postLoop = this.postLoop;

			return curve;
		}

		public float Evaluate(float position)
		{
			CurveKey first = keys[0];
			CurveKey last = keys[keys.Count - 1];

			if (position < first.Position)
			{
				switch (this.PreLoop)
				{
					case CurveLoopType.Constant:
						//constant
						return first.Value;

					case CurveLoopType.Linear:
						// linear y = a*x +b with a tangeant of last point
						return first.Value - first.TangentIn * (first.Position - position);

					case CurveLoopType.Cycle:
						//start -> end / start -> end
						int cycle = GetNumberOfCycle(position);
						float virtualPos = position - (cycle * (last.Position - first.Position));
						return GetCurvePosition(virtualPos);

					case CurveLoopType.CycleOffset:
						//make the curve continue (with no step) so must up the curve each cycle of delta(value)
						cycle = GetNumberOfCycle(position);
						virtualPos = position - (cycle * (last.Position - first.Position));
						return (GetCurvePosition(virtualPos) + cycle * (last.Value - first.Value));

					case CurveLoopType.Oscillate:
						//go back on curve from end and target start 
						// start-> end / end -> start
						cycle = GetNumberOfCycle(position);
						if (0 == cycle % 2f)//if pair
							virtualPos = position - (cycle * (last.Position - first.Position));
						else
							virtualPos = last.Position - position + first.Position + (cycle * (last.Position - first.Position));
						return GetCurvePosition(virtualPos);
				}
			}
			else if (position > last.Position)
			{
				int cycle;
				switch (this.PostLoop)
				{
					case CurveLoopType.Constant:
						//constant
						return last.Value;

					case CurveLoopType.Linear:
						// linear y = a*x +b with a tangeant of last point
						return last.Value + first.TangentOut * (position - last.Position);

					case CurveLoopType.Cycle:
						//start -> end / start -> end
						cycle = GetNumberOfCycle(position);
						float virtualPos = position - (cycle * (last.Position - first.Position));
						return GetCurvePosition(virtualPos);

					case CurveLoopType.CycleOffset:
						//make the curve continue (with no step) so must up the curve each cycle of delta(value)
						cycle = GetNumberOfCycle(position);
						virtualPos = position - (cycle * (last.Position - first.Position));
						return (GetCurvePosition(virtualPos) + cycle * (last.Value - first.Value));

					case CurveLoopType.Oscillate:
						//go back on curve from end and target start 
						// start-> end / end -> start
						cycle = GetNumberOfCycle(position);
						virtualPos = position - (cycle * (last.Position - first.Position));
						if (0 == cycle % 2f)//if pair
							virtualPos = position - (cycle * (last.Position - first.Position));
						else
							virtualPos = last.Position - position + first.Position + (cycle * (last.Position - first.Position));
						return GetCurvePosition(virtualPos);
				}
			}

			//in curve
			return GetCurvePosition(position);
		}

		#endregion Public Methods


		#region Private Methods

		private int GetNumberOfCycle(float position)
		{
			float cycle = (position - keys[0].Position) / (keys[keys.Count - 1].Position - keys[0].Position);
			if (cycle < 0f)
				cycle--;
			return (int)cycle;
		}

		private float GetCurvePosition(float position)
		{
			//only for position in curve
			CurveKey prev = this.keys[0];
			CurveKey next;
			for (int i = 1; i < this.keys.Count; i++)
			{
				next = this.Keys[i];
				if (next.Position >= position)
				{
					if (prev.Continuity == CurveContinuity.Step)
					{
						if (position >= 1f)
						{
							return next.Value;
						}
						return prev.Value;
					}
					float t = (position - prev.Position) / (next.Position - prev.Position);//to have t in [0,1]
					float ts = t * t;
					float tss = ts * t;
					//After a lot of search on internet I have found all about spline function
					// and bezier (phi'sss ancien) but finaly use hermite curve 
					//http://en.wikipedia.org/wiki/Cubic_Hermite_spline
					//P(t) = (2*t^3 - 3t^2 + 1)*P0 + (t^3 - 2t^2 + t)m0 + (-2t^3 + 3t^2)P1 + (t^3-t^2)m1
					//with P0.value = prev.value , m0 = prev.tangentOut, P1= next.value, m1 = next.TangentIn
					return (2 * tss - 3 * ts + 1f) * prev.Value + (tss - 2 * ts + t) * prev.TangentOut + (3 * ts - 2 * tss) * next.Value + (tss - ts) * next.TangentIn;
				}
				prev = next;
			}
			return 0f;
		}

		#endregion
	}
}
